﻿// main.cpp : Defines the entry point for the application.
#include "RootRpcServer.h"
#include "main.h"

FILE * sharedLog = nullptr;
FILE * errorLog =	stderr;
FILE * warnLog =	stderr;
FILE * infoLog =	stdout;
FILE * verboseLog =	stdout;
FILE * debugLog =	stdout;
DWORD loglevel = DEFAULT_LOG_LEVEL;
PWSTR logfile = DEFAULT_LOG_FILE;

SERVICE_STATUS svcstatus = { 0 };
SERVICE_STATUS_HANDLE statushandle = nullptr;
SC_HANDLE schandle = nullptr;
SC_HANDLE servicehandle = nullptr;

HKEY regkey = nullptr;

int wmain(int argc, wchar_t ** argv)
{
	OPERATION op = OPERATION::OPERATION_UNKNOWN;
	WINERR err = NO_ERROR;
	SERVICE_TABLE_ENTRYW table[2];
	table[0].lpServiceName = (LPWSTR)SERVICE_NAME;
	table[0].lpServiceProc = (LPSERVICE_MAIN_FUNCTIONW)ServiceMain;
	table[1].lpServiceName = nullptr;
	table[1].lpServiceProc = nullptr;

	if (argc > 1)
	{
		for (int i = 1; i < argc; i++)
		{
			if (!::_wcsicmp(L"/?", argv[i]))
			{
				if (OPERATION_UNKNOWN == op)
				{ 
					op = OPERATION_HELP;
				}
				else
				{
					LogError(L"Illegal use of %s; another operation was already specified!\n",
						argv[i]);
					return ERROR_BAD_ARGUMENTS;
				}
			}
			else if (!::_wcsicmp(L"/help", argv[i]))
			{
				if (OPERATION_UNKNOWN == op)
				{
					op = OPERATION_HELP;
				}
				else
				{
					LogError(L"Illegal use of %s; another operation was already specified!\n",
						argv[i]);
					return ERROR_BAD_ARGUMENTS;
				}
			}
			else if (!::_wcsicmp(L"/install", argv[i]))
			{
				if (OPERATION_UNKNOWN == op)
				{
					op = OPERATION_INSTALL;
				}
				else
				{
					LogError(L"Illegal use of %s; another operation was already specified!\n",
						argv[i]);
					return ERROR_BAD_ARGUMENTS;
				}
			}
			else if (!::_wcsicmp(L"/update", argv[i]))
			{
				if (OPERATION_UNKNOWN == op)
				{
					op = OPERATION_UPDATE;
				}
				else
				{
					LogError(L"Illegal use of %s; another operation was already specified!\n",
						argv[i]);
					return ERROR_BAD_ARGUMENTS;
				}
			}
			else if (!::_wcsicmp(L"/start", argv[i]))
			{
				if (OPERATION_UNKNOWN == op)
				{
					op = OPERATION_START;
				}
				else
				{
					LogError(L"Illegal use of %s; another operation was already specified!\n",
						argv[i]);
					return ERROR_BAD_ARGUMENTS;
				}
			}
			else if (!::_wcsicmp(L"/stop", argv[i]))
			{
				if (OPERATION_UNKNOWN == op)
				{
					op = OPERATION_STOP;
				}
				else
				{
					LogError(L"Illegal use of %s; another operation was already specified!\n",
						argv[i]);
					return ERROR_BAD_ARGUMENTS;
				}
			}
			else if (!::_wcsicmp(L"/delete", argv[i]))
			{
				if (OPERATION_UNKNOWN == op)
				{
					op = OPERATION_DELETE;
				}
				else
				{
					LogError(L"Illegal use of %s; another operation was already specified!\n",
						argv[i]);
					return ERROR_BAD_ARGUMENTS;
				}
			}
			else if (!::_wcsicmp(L"/q", argv[i]))
			{
				if (loglevel)
				{
					loglevel--;
				}
			}
			else if (!::_wcsicmp(L"/v", argv[i]))
			{
				if (loglevel < LOG_LEVEL_DEBUG)
				{
					loglevel++;
				}
			}
			else
			{
				::wprintf(L"Unrecognized command \"%s\" found\n", argv[i]);
				PrintHelp();
				return ERROR_BAD_ARGUMENTS;
			}
		}
		// Update to the requested logging level.
		SetLogPtrs();
		switch (op)
		{
		case OPERATION_UNKNOWN:
		case OPERATION_HELP:
			// Display info, but nothing else.
			PrintHelp();
			break;
		case OPERATION_INSTALL:
			err = InstallTheService();
			LogDebug(L"InstallTheService returned %d\n.", err);
			break;
		case OPERATION_UPDATE:
			err = UpdateTheService();
			LogDebug(L"UpdateTheService returned %d\n.", err);
			break;
		case OPERATION_DELETE:
			err = DeleteTheService();
			LogDebug(L"DeleteTheService returned %d.\n", err);
			break;
		case OPERATION_START:
			err = StartTheService();
			LogDebug(L"StartTheService returned %d.\n", err);
			break;
		case OPERATION_STOP:
			err = StopTheService();
			LogDebug(L"StopTheService returned %d.\n", err);
			break;
		}
		return err;
	}	// if (argc > 1)
	// No arguments means service mode.
	// Open the configuration registry key
	if (err = ::RegCreateKeyExW(
		HKEY_LOCAL_MACHINE,
		REGPATH,
		0x0,
		nullptr,
		REG_OPTION_NON_VOLATILE,
		KEY_ALL_ACCESS,
		nullptr,
		&regkey,
		nullptr))
	{	// An error occurred. That shouldn't have happened.
		return err;
	}
	// Get the logging level
	DWORD size = sizeof(DWORD);
	err = ::RegQueryValueExW(
		regkey,
		LOGLEVELVALUE,
		nullptr,
		nullptr,
		reinterpret_cast<LPBYTE>(&loglevel),
		&size);
	if (err || loglevel >= LOG_LEVEL_MAX)
	{	// Probably just doesn't exist, but do the same thing in all cases
		SetLogLevel(DEFAULT_LOG_LEVEL);
	}
	// Get the log path
	// First, get the size
	size = MAX_PATH;
	err = ::RegQueryValueExW(
		regkey,
		LOGFILEVALUE,
		nullptr,
		nullptr,
		nullptr,
		&size);
	if (err)
	{	// Can't get the size, even. Probably doesn't exist. Use default.
		// We don't really care if this succeeds or not.
		SetLogFile(DEFAULT_LOG_FILE);
	}
	else
	{	// We have the size now
		logfile = new WCHAR[size];
		if (!logfile)
		{	// Out of memory, really?
			fwprintf(stderr, L"ERROR: Unable to allocate memory to get log path!\n");
			return ERROR_OUTOFMEMORY;
		}
		LSTATUS err = ::RegQueryValueExW(
			regkey,
			LOGFILEVALUE,
			nullptr,
			nullptr,
			reinterpret_cast<LPBYTE>(logfile),
			&size);
		if (err)
		{	// Not even going to check for more_data, that's silly, just default it.
			delete[] logfile;
			SetLogFile(DEFAULT_LOG_FILE);
		}
		else
		{
			SetLogFile(logfile);
		}
	}
	if (!::StartServiceCtrlDispatcherW(table))
	{
		err = ::GetLastError();
		// In case this happened while attempting to run as a service, log and print it.
		LogError(L"StartServiceCtrlDispatcherW failed, Win32 error code %d!\n", err);
		::fwprintf(stderr, L"StartServiceCtrlDispatcherW failed, Win32 error code %d!\n", err);
		if (ERROR_FAILED_SERVICE_CONTROLLER_CONNECT == err)
		{
			// This happens if you try to start the service dispatcher when not a service process.
			::wprintf(L"Are you attempting to run the RPC server in non-service mode?\n");
			PrintHelp();
		}
		return err;
	}
	// If we get here, service stopped.
	LogVerbose(L"Server process exiting with status %d.\n", err);
	if (sharedLog)
	{
		::fwprintf(sharedLog, L"\n");
		::fflush(sharedLog);
	}
	return err;
}

void PrintHelp()
{
	::wprintf(L"RootRpc Server version %d.%d.%d (%d)\n", 
		VER_MAJOR, VER_MINOR, VER_UPDATE, VER_BUILD);
	::wprintf(L"USAGE: RootRpcServer <OPERATION> [OPTIONS]\n");
	::wprintf(L"       RootRpcServer [/?][/help]\n");
	//::wprintf(L"       RootRpcServer /nonsvc\n");
	::wprintf(L"\t/?, /help:\tPrint this usage information.\n");
	::wprintf(L"OPERATIONS: /install | /update | /delete | /start | /stop\n");
	::wprintf(L"\t/install: Register the %s service to start on demand.\n", DISPLAY_NAME);
	::wprintf(L"\t/update:  Use a new version of the service, preserving settings.\n", DISPLAY_NAME);
	::wprintf(L"\t/delete:  Un-register the %s service and delete settings.\n", DISPLAY_NAME);
	::wprintf(L"\t/start:   Start the %s service (in its own process).\n", DISPLAY_NAME);
	::wprintf(L"\t/stop:    Stop the running %s service instance.\n", DISPLAY_NAME);
	::wprintf(L"OPTIONS: /q | /v\n");
	::wprintf(L"\t/q:       Quiets the output. May be specified 1 - 3 times.\n", DISPLAY_NAME);
	::wprintf(L"\t/v:       Makes output verbose. May be specified 1 or 2 times.\n", DISPLAY_NAME);

	//PSECURITY_DESCRIPTOR psd;
	//LPWSTR ssd;
	//::QueryTransientObjectSecurityDescriptor(TRANSIENT_RPC, L"DefaultInterface", &psd);
	//::ConvertSecurityDescriptorToStringSecurityDescriptorW(
	//	psd, SDDL_REVISION, 0x1007F, &ssd, nullptr);
	//::FreeTransientObjectSecurityDescriptor(psd);
	//::wprintf(L"Transient object security for RPC\\DefaultInterface:\n%s.\n", ssd);
	//::LocalFree(ssd);
	//::QueryTransientObjectSecurityDescriptor(TRANSIENT_RPC, L"InteropRPC\\Server", &psd);
	//::ConvertSecurityDescriptorToStringSecurityDescriptorW(
	//	psd, SDDL_REVISION, 0x1007F, &ssd, nullptr);
	//::FreeTransientObjectSecurityDescriptor(psd);
	//::wprintf(L"Transient object security for RPC\\InteropRPC\\Server:\n%s.\n", ssd);
	//::LocalFree(ssd);
}

WINERR InstallTheService()
{
	WINERR err = NO_ERROR;
	DWORD len = MAX_PATH;
	wchar_t processname[MAX_PATH];
	PSECURITY_DESCRIPTOR psd = nullptr;
	ULONG psdsize = 0x0;
	LogInfo(L"Installing the %s service...\n", SERVICE_NAME);
	// Move the client libraries to System32. Apps won't see them otherwise.
	// TODO: Consider using KnownDlls, or otherwise registering the DLLs?
	if ((err = InstallClients()))
	{
		LogError(L"Unable to install client DLLs!\n");
		return err;
	}
	err = CreateCapability();
	LogDebug(L"CreateCapability returned %d.\n", err);
	if (err)
	{
		return err;
	}
	// Get the full path and name of this process
	if (!::QueryFullProcessImageNameW(::GetCurrentProcess(), 0x0, processname, &len))
	{
		err = ::GetLastError();
		::LogError(L"GetProcessImageFileNameW failed with error %d!\n", err);
		return err;
	}
	::LogDebug(L"QueryFullIProcessImageNameW succeeded: \"%s\"\n", processname);
	// Open a handle to the service control manager, with rights to create a service.
	// Don't bother checking if it's already open; this function always goes first.
	schandle = ::OpenSCManagerW(nullptr, nullptr, SC_MANAGER_ALL_ACCESS);
	if (!schandle)
	{
		err = ::GetLastError();
		LogError(L"OpenSCManagerW failed with error %d!\n", err);
		return err;
	}
	LogDebug(L"OpenSCManagerW(null, null, ALL_ACCESS) succeeded.\n");
	// Create a manual-start own-service for this process.
	servicehandle = ::CreateServiceW(
		schandle,					// HANDLE to the service control manager
		SERVICE_NAME,				// Name used to programmatically refer to the service
		DISPLAY_NAME,				// Human-readable "display name" for the service
		SERVICE_ALL_ACCESS,			// Access to the returned service HANDLE
		SERVICE_WIN32_OWN_PROCESS,	// Type of service
		SERVICE_DEMAND_START,		// Start behavior for service
		SERVICE_ERROR_IGNORE,		// Effect of an error while starting service
		processname,				// File pathname of the service
		nullptr,					// Load order group
		nullptr,					// Receives a tag unique within load ordering group
		nullptr,					// Multi-string list of dependencies
		nullptr,					// Account under which the service runs (or LOCALSYSTEM)
		nullptr);					// Password for the account
	if (!servicehandle)
	{
		err = ::GetLastError();
		LogError(L"CreateServiceW failed with Win32 error code %d\n", err);
		return err;
	}
	LogDebug(L"CreateServiceW succeeded.\n");
	LogVerbose(L"Service has been created but not yet configured.\n");
	// Create a security descriptor that allows RootRpc apps to query or start the service.
	if (!::ConvertStringSecurityDescriptorToSecurityDescriptorW(
		SDDL_STRING,				// The SDDL string specifying the security descriptor
		SDDL_REVISION_1,			// Required; specifies version "1"
		&psd,						// Recieves a pointer to a self-relative (packed) SD
		&psdsize))					// Receives the size of the SD
	{
		err = ::GetLastError();
		LogError(
			L"ConvertStringSecurityDescriptorToSecurityDescriptorW failed, Win32 error %d!\n",
			err);
		return err;
	}
	LogDebug(L"ConvertStringSecurityDescriptorToSecurityDescriptorW succeeded.\n");
	// Set the security on the process so that anybody can start it.
	if (!::SetServiceObjectSecurity(servicehandle, DACL_SECURITY_INFORMATION, psd))
	{
		err = ::GetLastError();
		LogError(L"SetServiceObjectSecurity failed, Win32 error code %d!\n", err);
		return err;
	}
	LogDebug(L"SetServiceObjectSecurity(service, DACL, psd) succeeded.\n");
	if (::LocalFree(psd))
	{
		err = ::GetLastError();
		LogWarning(L"LocalFree failed with Win32 error %d!\n", err);
	}
	// TODO: Add a service description
	LogInfo(L"Service \"%s\" (%s) installed successfully.\n", SERVICE_NAME, DISPLAY_NAME);
	return err;
}

WINERR UpdateTheService()
{
	WINERR err = NO_ERROR;
	DWORD len = MAX_PATH;
	wchar_t processname[MAX_PATH];
	PSECURITY_DESCRIPTOR psd = nullptr;
	ULONG psdsize = 0x0;
	// Move the client libraries to System32. Apps won't see them otherwise.
	// TODO: Consider using KnownDlls, or otherwise registering the DLLs?
	if ( (err = InstallClients()) )
	{
		LogError(L"Unable to install client DLLs!\n");
		return err;
	}
	// Get the full path and name of this process
	if (!::QueryFullProcessImageNameW(::GetCurrentProcess(), 0x0, processname, &len))
	{
		err = ::GetLastError();
		::LogError(L"GetProcessImageFileNameW failed with error %d!\n", err);
		return err;
	}
	::LogDebug(L"QueryFullIProcessImageNameW succeeded: \"%s\"\n", processname);
	// Open a handle to the service control manager, with rights to create a service.
	schandle = ::OpenSCManagerW(nullptr, nullptr, SC_MANAGER_ALL_ACCESS);
	if (!schandle)
	{
		err = ::GetLastError();
		LogError(L"OpenSCManagerW failed with error %d!\n", err);
		return err;
	}
	LogDebug(L"OpenSCManagerW(null, null, ALL_ACCESS) succeeded.\n");
	servicehandle = ::OpenServiceW(schandle, SERVICE_NAME, SERVICE_ALL_ACCESS);
	if (!servicehandle)
	{
		err = ::GetLastError();
		LogError(L"OpenServiceW failed with error %d!\n", err);
		return err;
	}
	LogDebug(L"OpenServiceW(schandle, " SERVICE_NAME L", ALL_ACCESS) succeeded.\n");
	// TODO: Check whether the service is currently running!
	if (!::ChangeServiceConfigW(
		servicehandle,
		SERVICE_WIN32_OWN_PROCESS,
		SERVICE_DEMAND_START,
		SERVICE_ERROR_IGNORE,
		processname,
		nullptr,
		nullptr,
		nullptr,
		nullptr,
		nullptr,
		DISPLAY_NAME))
	{
		err = ::GetLastError();
		LogError(L"ChangeServiceConfigW failed with error %d!\n", err);
		return err;
	}
	// Create a security descriptor that allows RootRpc apps to query or start the service.
	if (!::ConvertStringSecurityDescriptorToSecurityDescriptorW(
		SDDL_STRING,				// The SDDL string specifying the security descriptor
		SDDL_REVISION_1,			// Required; specifies version "1"
		&psd,						// Recieves a pointer to a self-relative (packed) SD
		&psdsize))					// Receives the size of the SD
	{
		err = ::GetLastError();
		LogError(
			L"ConvertStringSecurityDescriptorToSecurityDescriptorW failed, Win32 error %d!\n",
			err);
		return err;
	}
	LogDebug(L"ConvertStringSecurityDescriptorToSecurityDescriptorW succeeded.\n");
	// Set the security on the process so that anybody can start it.
	if (!::SetServiceObjectSecurity(servicehandle, DACL_SECURITY_INFORMATION, psd))
	{
		err = ::GetLastError();
		LogError(L"SetServiceObjectSecurity failed, Win32 error code %d!\n", err);
		return err;
	}
	LogDebug(L"SetServiceObjectSecurity(service, DACL, psd) succeeded.\n");
	if (::LocalFree(psd))
	{
		err = ::GetLastError();
		LogWarning(L"LocalFree failed with Win32 error %d!\n", err);
	}
	// TODO: Add a service description
	LogInfo(L"Service \"%s\" (%s) updated successfully.\n", DISPLAY_NAME, SERVICE_NAME);
	return err;
}

WINERR DeleteTheService()
{
	WINERR err = NO_ERROR;
	bool closeservice = false;
	bool closescm = false;
	LogInfo(L"Deleting the %s service...\n", SERVICE_NAME);
#ifdef CREATE_ID_CAP_ROOTRPC
	// First delete the ID_CAP_ROOTRPC capability
	err = ::RegDeleteKeyExW(HKEY_LOCAL_MACHINE, ID_CAP_ROOTRPC_KEYPATH, KEY_WOW64_64KEY, 0x0);
	if (err)
	{
		LogWarning(L"Deleting registry key \"HKLM\\" ID_CAP_ROOTRPC_KEYPATH L"\" failed with error "
			L"%d!\n", err);
	}
	// Delete the values mapping the app SID and service SID to the name
	err = ::RegDeleteKeyValueW(HKEY_LOCAL_MACHINE, CAPABILITIES_KEYPATH, ID_CAP_ROOTRPC_APP_STRING);
	if (err)
	{
		LogWarning(L"Deleting registry value \"" ID_CAP_ROOTRPC_APP_STRING L"\" failed with error "
			L"%d!\n", err);
	}
	err = ::RegDeleteKeyValueW(HKEY_LOCAL_MACHINE, CAPABILITIES_KEYPATH, ID_CAP_ROOTRPC_SVC_STRING);
	if (err)
	{
		LogWarning(L"Deleting registry value \"" ID_CAP_ROOTRPC_SVC_STRING L"\" failed with error "
			L"%d!\n", err);
	}
	// Delete the CapabilityClasses entry
	err = ::RegDeleteKeyValueW(HKEY_LOCAL_MACHINE, CAPABILITYCLASSES_KEYPATH, ID_CAP_ROOTRPC);
	if (err)
	{
		LogWarning(L"Deleting registry value \"" ID_CAP_ROOTRPC L"\" failed with error %d!\n", err);
	}
	LogVerbose(L"Deletion of ID_CAP_ROOTRPC complete. Some apps may still have it declared.\n");
#endif
	// Make sure the service HANDLE is available
	if (!servicehandle)
	{
		if (!schandle)
		{
			schandle = ::OpenSCManagerW(nullptr, nullptr, SC_MANAGER_CONNECT);
			if (!schandle)
			{
				err = ::GetLastError();
				LogError(L"OpenSCManagerW failed, Win32 error code %d!\n", err);
				goto Cleanup;
			}
			closescm = true;
		}
		servicehandle = ::OpenServiceW(schandle, SERVICE_NAME, DELETE);
		if (!servicehandle)
		{
			err = ::GetLastError();
			if (ERROR_SERVICE_DOES_NOT_EXIST == err)
			{
				LogWarning(L"Service %s was not installed!\n", SERVICE_NAME);
				servicehandle = ::OpenServiceW(schandle, OLD_SERVICE_NAME, DELETE);
				if (!servicehandle)
				{
					err = ::GetLastError();
					if (ERROR_SERVICE_DOES_NOT_EXIST == err)
					{
						LogWarning(L"Service %s was not installed!\n", OLD_SERVICE_NAME);
						goto Cleanup;
					}
					else
					{
						LogError(L"OpenServiceW failed, Win32 error code %d!\n", err);
						goto Cleanup;
					}
				}
			}
			else
			{
				LogError(L"OpenServiceW failed, Win32 error code %d!\n", err);
				goto Cleanup;
			}
		}
		closeservice = true;
	}
	// OK, we have the service HANDLE, and it should have the permissions.
	if (!::DeleteService(servicehandle))
	{
		err = ::GetLastError();
		LogError(L"DeleteService failed, Win32 error code %d!\n", err);
		goto Cleanup;
	}
	// Finally, delete the client libraries.
	if (!::DeleteFileW(L"C:\\Windows\\System32\\RootRpcClient.dll"))
	{
		err = ::GetLastError();
		LogError(L"Deleting RootRpcClient.dll failed, Win32 error code %d!\n", err);
	}
	if (!::DeleteFileW(L"C:\\Windows\\System32\\RootRpcClient_SL81.dll"))
	{
		err = ::GetLastError();
		LogError(L"Deleting RootRpcClient_SL81.dll failed, Win32 error code %d!\n", err);
	}
Cleanup:
	if (closescm)
	{
		::CloseServiceHandle(schandle);
		schandle = nullptr;
	}
	if (closeservice)
	{
		::CloseServiceHandle(servicehandle);
		servicehandle = nullptr;
	}
	return err;
}

WINERR StartTheService()
{
	WINERR err = NO_ERROR;
	bool closeservice = false;
	bool closescm = false;
	LogVerbose(L"Starting the %s service, if it is installed.\n", SERVICE_NAME);
	// First make sure the service HANDLE is available
	if (!servicehandle)
	{
		if (!schandle)
		{
			schandle = ::OpenSCManagerW(nullptr, nullptr, SC_MANAGER_CONNECT);
			if (!schandle)
			{
				err = ::GetLastError();
				LogError(L"OpenSCManagerW failed, Win32 error code %d!\n", err);
				goto Cleanup;
			}
			closescm = true;
		}
		servicehandle = ::OpenServiceW(
			schandle,
			SERVICE_NAME,
			SERVICE_QUERY_STATUS | SERVICE_START);
		if (!servicehandle)
		{
			err = ::GetLastError();
			LogError(L"OpenServiceW failed, Win32 error code %d!\n", err);
			goto Cleanup;
		}
		closeservice = true;
	}
	// OK, we have the service HANDLE. Don't bother to check the status, just go.
	if (!::StartServiceW(servicehandle, 0x0, nullptr))
	{
		err = ::GetLastError();
		if (ERROR_SERVICE_ALREADY_RUNNING == err)
		{
			LogWarning(L"Service \"%s\" is already running.\n", SERVICE_NAME);
			err = NO_ERROR;
		}
		else if (ERROR_SERVICE_DOES_NOT_EXIST == err)
		{
			LogError(L"Service is not installed!\n");
		}
		else
		{
			LogError(L"StartServiceW failed, Win32 error code %d!\n", err);
		}
		goto Cleanup;
	}
	LogInfo(L"%s service started successfully!\n", DISPLAY_NAME);
Cleanup:
	if (closescm)
	{
		::CloseServiceHandle(schandle);
		schandle = nullptr;
	}
	if (closeservice)
	{
		::CloseServiceHandle(servicehandle);
		servicehandle = nullptr;
	}
	return err;
}

WINERR StopTheService()
{
	DWORD err = NO_ERROR;
	bool closeservice = false;
	bool closescm = false;
	LogInfo(L"Stopping the %s service...\n", SERVICE_NAME);
	// First make sure the service HANDLE is available
	if (!servicehandle)
	{
		if (!schandle)
		{
			schandle = ::OpenSCManagerW(nullptr, nullptr, SC_MANAGER_CONNECT);
			if (!schandle)
			{
				err = ::GetLastError();
				LogError(L"OpenSCManagerW failed, Win32 error code %d!\n", err);
				goto Cleanup;
			}
			closescm = true;
			LogDebug(L"OpenSCManagerW(nullptr, nullptr, SC_MANAGER_CONNECT) succeeded.\n");
		}
		servicehandle = ::OpenServiceW(
			schandle,
			SERVICE_NAME,
			SERVICE_STOP);
		if (!servicehandle)
		{
			err = ::GetLastError();
			LogError(L"OpenServiceW failed, Win32 error code %d!\n", err);
			goto Cleanup;
		}
		closeservice = true;
		LogDebug(L"OpenServiceW(schandle, \"%s\", SERVICE_STOP) succeeded.\n", SERVICE_NAME);
	}
	// OK, we have the service HANDLE. Don't bother to check the status, just go.
	if (!::ControlService(servicehandle, SERVICE_CONTROL_STOP, &svcstatus))
	{
		err = ::GetLastError();
		if (ERROR_SERVICE_NOT_ACTIVE == err)
		{
			LogWarning(L"Service was not running!\n");
		}
		else
		{
			LogError(L"ControlService failed, Win32 error code %d!\n", err);
		}
		goto Cleanup;
	}
	else
	{
		LogDebug(L"ControlService(servicehandle, SERVICE_CONTROL_STOP, &svcstatus) succeeded.\n");
	}
	LogVerbose(
		L"Service final status was %d and exit code was %d.\n",
		svcstatus.dwCurrentState,
		svcstatus.dwWin32ExitCode);
Cleanup:
	if (closescm)
	{
		::CloseServiceHandle(schandle);
		schandle = nullptr;
	}
	if (closeservice)
	{
		::CloseServiceHandle(servicehandle);
		servicehandle = nullptr;
	}
	if (svcstatus.dwWin32ExitCode)
	{
		err = svcstatus.dwWin32ExitCode;
	}
	return err;
}

WINERR CreateCapability()
{
	WINERR err = NO_ERROR;
	HKEY hkRootRpc = nullptr;
	DWORD dw = 0x0;
#ifdef CREATE_ID_CAP_ROOTRPC
	// Create the ID_CAP_ROOTRPC
	err = ::RegCreateKeyExW(
		HKEY_LOCAL_MACHINE,
		ID_CAP_ROOTRPC_KEYPATH,
		0x0,
		nullptr,
		REG_OPTION_NON_VOLATILE,
		KEY_ALL_ACCESS,
		nullptr,
		&hkRootRpc,
		nullptr);
	if (err)
	{
		LogError(L"Creating registry key HKLM\\" ID_CAP_ROOTRPC_KEYPATH
			L" failed with error %d!\n", err);
		return err;
	}
	err = ::RegSetValueExW(
		hkRootRpc,
		L"ApplicationCapabilitySID",
		0x0,
		REG_BINARY,
		ID_CAP_ROOTRPC_APP_SID,
		sizeof(ID_CAP_ROOTRPC_APP_SID));
	if (err)
	{
		LogError(L"Setting registry value \"ApplicationCapabilitySID\" failed with error %d!\n",
			err);
		return err;
	}
	err = ::RegSetValueExW(
		hkRootRpc,
		L"CapabilityType",
		0x0,
		REG_DWORD,
		reinterpret_cast<PBYTE>(&dw),
		sizeof(DWORD));
	if (err)
	{
		LogError(L"Setting registry value \"CapabilityType\" failed with error %d!\n", err);
		return err;
	}
	err = ::RegSetValueExW(
		hkRootRpc,
		L"EmbeddedWindowsCapabilitySIDs",
		0x0,
		REG_BINARY,
		reinterpret_cast<PBYTE>(&dw),
		0x4);
	if (err)
	{
		LogError(L"Setting registry value \"EmbeddedWindowsCapabilitySIDs\" failed with error "
			L"%d!\n", err);
		return err;
	}
	err = ::RegSetValueExW(
		hkRootRpc,
		L"Privileges",
		0x0,
		REG_BINARY,
		reinterpret_cast<PBYTE>(&dw),
		0x4);
	if (err)
	{
		LogError(L"Setting registry value \"Privileges\" failed with error %d!\n", err);
		return err;
	}
	err = ::RegSetValueExW(
		hkRootRpc,
		L"ServiceCapabilitySID",
		0x0,
		REG_BINARY,
		ID_CAP_ROOTRPC_SVC_SID,
		sizeof(ID_CAP_ROOTRPC_SVC_SID));
	if (err)
	{
		LogError(L"Setting registry value \"ServiceCapabilitySID\" failed with error %d!\n",
			err);
		return err;
	}
	// Done with SecurityManager\Capabilities\ID_CAP_ROOTRPC
	::RegCloseKey(hkRootRpc);
	err = ::RegOpenKeyExW(
		HKEY_LOCAL_MACHINE,
		CAPABILITIES_KEYPATH,
		0x0,
		KEY_ALL_ACCESS,
		&hkRootRpc);
	if (err)
	{
		LogError(L"Opening registry key HKLM\\" CAPABILITIES_KEYPATH L" failed with error %d!\n",
			err);
		return err;
	}
	err = ::RegSetValueExW(
		hkRootRpc,
		ID_CAP_ROOTRPC_APP_STRING,
		0x0,
		REG_SZ,
		reinterpret_cast<LPCBYTE>(ID_CAP_ROOTRPC),
		sizeof(ID_CAP_ROOTRPC));
	if (err)
	{
		LogError(L"Setting registry value \"" ID_CAP_ROOTRPC_APP_STRING L"\" failed with error "
			L"%d!\n", err);
		return err;
	}
	err = ::RegSetValueExW(
		hkRootRpc,
		ID_CAP_ROOTRPC_SVC_STRING,
		0x0,
		REG_SZ,
		reinterpret_cast<LPCBYTE>(ID_CAP_ROOTRPC),
		sizeof(ID_CAP_ROOTRPC));
	if (err)
	{
		LogError(L"Setting registry value \"" ID_CAP_ROOTRPC_SVC_STRING L"\" failed with error "
			L"%d!\n", err);
		return err;
	}
	// Done with SecurityManager\Capabilities
	::RegCloseKey(hkRootRpc);
#endif
	err = ::RegOpenKeyExW(
		HKEY_LOCAL_MACHINE,
		CAPABILITYCLASSES_KEYPATH,
		0x0,
		KEY_ALL_ACCESS,
		&hkRootRpc);
	if (err)
	{
		LogError(L"Opening registry key HKLM\\" CAPABILITYCLASSES_KEYPATH L" failed with error "
			L"%d!\n", err);
		return err;
	}
	err = ::RegSetValueExW(
		hkRootRpc,
		ID_CAP_ROOTRPC,
		0x0,
		REG_MULTI_SZ,
		reinterpret_cast<LPCBYTE>(THIRD_PARTY_CLASS),
		sizeof(THIRD_PARTY_CLASS));
	if (err)
	{
		LogError(L"Setting registry value \"" ID_CAP_ROOTRPC L"\" failed with error "
			L"%d!\n", err);
		return err;
	}
	// TODO: Make sure the HKEY always gets closed (or don't; we exit almost immediately anyhow).
	::RegCloseKey(hkRootRpc);
	LogVerbose(L"Successfully created the " ID_CAP_ROOTRPC L" capability.\n");
	return err;
}

WINERR InstallClients()
{
	WINERR err = NO_ERROR;
	if (!MoveFileExW(
		L"RootRpcClient.dll",
		L"C:\\Windows\\System32\\RootRpcClient.dll",
		MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED))
	{
		err = ::GetLastError();
		LogError(L"Failed to move file RootRpcClient.dll! Error %d.\n", err);
	}
	else if (!MoveFileExW(
		L"RootRpcClient_SL81.dll",
		L"C:\\Windows\\System32\\RootRpcClient_SL81.dll",
		MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED))
	{
		err = ::GetLastError();
		LogError(L"Failed to move file RootRpcClient_SL81.dll! Error %d.\n",
			err);
	}
	else
	{
		LogVerbose(L"Successfully installed client libraries to System32.\n");
	}
	return err;
}

DWORD WINAPI HandlerEx(DWORD dwControl, DWORD dwEventType, LPVOID lpEventData, LPVOID lpContext)
{
	LogVerbose(L"Service received control code %d.\n", dwControl);
	switch (dwControl)
	{
	case SERVICE_CONTROL_STOP:
		// De-initialize, shut down RPC, and report stopping.
		ServiceStop();
		//svcstatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
		//svcstatus.dwCurrentState = SERVICE_STOPPED;
		//svcstatus.dwControlsAccepted = 0x0;
		//svcstatus.dwWin32ExitCode = NO_ERROR;
		//svcstatus.dwServiceSpecificExitCode = NO_ERROR;
		//svcstatus.dwCheckPoint = 0x0;
		//svcstatus.dwWaitHint = 0x0;
		//if (!::SetServiceStatus(statushandle, &svcstatus))
		//{
		//	return ::GetLastError();
		//}
		// Always return NO_ERROR for this control code
		return NO_ERROR;
	case SERVICE_CONTROL_INTERROGATE:
		return NO_ERROR;
	default:
		LogWarning(L"Unsupported service control code %d received!\n", dwControl);
		return ERROR_CALL_NOT_IMPLEMENTED;
	}
}

void WINAPI ServiceMain(DWORD argc, LPWSTR * argv)
{
	WINERR err = NO_ERROR;
	// First thing to do is register the service control handler function.
	statushandle = ::RegisterServiceCtrlHandlerExW(SERVICE_NAME, HandlerEx, nullptr);
	if (!statushandle)
	{
		// We can't even set a service status!
		// Just return; the service control manager will kill us when we time out.
		LogError(
			L"RegisterServiceCtrlHandlerExW failed, Win32 error %d!\n",
			::GetLastError());
		return;
	}
	LogDebug(
		L"RegisterServiceCtrlHandlerExW(\"%s\", HandlerEx, nullptr) succeeded.\n",
		SERVICE_NAME);
	// Perform initialization. Let's let the server know we're starting up.
	svcstatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;		// Running as our own process
	svcstatus.dwCurrentState = SERVICE_START_PENDING;			// Starting up, not ready yet
	svcstatus.dwControlsAccepted = 0x0;							// Not accepting controls yet
	svcstatus.dwWin32ExitCode = NO_ERROR;						// No error at this point
	svcstatus.dwServiceSpecificExitCode = NO_ERROR;				// Not using service-specic error
	svcstatus.dwCheckPoint = 0x1;								// Start checkpoint count at 1
	svcstatus.dwWaitHint = 0x400;								// Set wait timout to a second
	if (!::SetServiceStatus(statushandle, &svcstatus))
	{
		// Well, that's awkward. Probably can't tell the SCM anything now...
		err = ::GetLastError();
		LogError(L"SetServiceStatus failed pre-init, Win32 error %d!\n", err);
		// But we'll try, anyhow.
		ServiceStop(err);
		return;
	}
	LogDebug(L"SetServiceStatus for SERVICE_START_PENDING succeeded.\n");
	// OK, now initialize the RPC server
	err = StartRpcServer();
	if (err)
	{
		// Something went wrong starting the server. Tell the SCM that we're going down again.
		LogWarning(L"StartRpcServer failed, so shutting down service.\n");
		ServiceStop(err);
		return;
	}
	// RPC server has started, and is listening. Tell the SCM we are good to go!
	svcstatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;		// Running as our own process
	svcstatus.dwCurrentState = SERVICE_RUNNING;					// Init completed, doing our thing
	svcstatus.dwControlsAccepted = SERVICE_ACCEPT_STOP;			// Let the SCM tell us to stop
	svcstatus.dwWin32ExitCode = NO_ERROR;						// No error at this point
	svcstatus.dwServiceSpecificExitCode = NO_ERROR;				// Not using service-specic error
	svcstatus.dwCheckPoint = 0x0;								// New operation, checkpoint at 0
	svcstatus.dwWaitHint = 0x0;									// Don't need to wait for anything
	if (!::SetServiceStatus(statushandle, &svcstatus))
	{
		// This is also awkward! The SCM will probably just time out waiting...
		err = ::GetLastError();
		LogError(L"SetServiceStatus failed post-init, Win32 error %d!\n", err);
		// But we'll try, anyhow.
		ServiceStop(err);
		return;
	}
	// We're done! Just return; the SCM will keep this thread for control dispatch.
	LogInfo(L"RPC server started and service running.\n");
}

WINERR ServiceStop(WINERR win32err)
{
	WINERR err = win32err;
	// Time to shut ourselves down
	LogDebug(L"ServiceStop(%d) called.\n", win32err);
	svcstatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;		// Running as our own process
	svcstatus.dwCurrentState = SERVICE_STOP_PENDING;			// Shut down in progress
	svcstatus.dwControlsAccepted = 0x0;							// Not accepting controls anymore
	svcstatus.dwWin32ExitCode = win32err;						// Report the code we were given
	svcstatus.dwServiceSpecificExitCode = NO_ERROR;				// Not using service-specic error
	svcstatus.dwCheckPoint = 0x1;								// Start checkpoint count at 1
	svcstatus.dwWaitHint = 0x400;								// Set wait timout to a second
	if (!::SetServiceStatus(statushandle, &svcstatus))
	{
		// Well, that's inconvenient. I guess we may just need to exit the hard way.
		LogError(L"SetServiceStatus failed during pre-stop with error %d!\n", ::GetLastError());
		err = StopRpcServer(FALSE);
	}
	else if (err)
	{	// We're shutting down with an error anyhow; shut down hard.
		err = StopRpcServer(FALSE);
	}
	else
	{	// OK, we have time to exit gracefully.
		LogDebug(L"SetServiceStatus with SERVICE_STOP_PENDING succeeded.\n");
		err = StopRpcServer();
	}
	if (err)
	{
		// Log this, and return it to the SCM if no other exit code specified.
		LogError(L"StopRpcServer failed during service shutdown with status %d!\n", err);
		if (!win32err)
		{
			win32err = err;
		}
	}
	// We're done here. Let the SCM know to kill us off.
	svcstatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;		// Running as our own process
	svcstatus.dwCurrentState = SERVICE_STOPPED;					// Service shut down
	svcstatus.dwControlsAccepted = 0x0;							// Not accepting controls anymore
	svcstatus.dwWin32ExitCode = win32err;						// Report the current error code
	svcstatus.dwServiceSpecificExitCode = NO_ERROR;				// Not using service-specic error
	svcstatus.dwCheckPoint = 0x0;								// No checkpoint
	svcstatus.dwWaitHint = 0x0;									// No timeout, we're done
	if (!::SetServiceStatus(statushandle, &svcstatus))
	{
		// Fine, be that way. We'll show ourself out, then.
		err = ::GetLastError();
		LogError(
			L"SetServiceStatus for SERVICE_STOPPED and Win32ExitCode %d failed with status %d, exiting!\n",
			win32err, err);
		::ExitProcess(win32err ? win32err : err);
	}
	// We may not actually get to do this.
	// After a successful SERVICE_STOPPED, the process may be terminated at any time.
	LogDebug(L"SetServiceStatus with SERVICE_STOPPED succeeded.\n");
	LogInfo(L"RPC server shut down and service stopped.\n");
	return err;
}

void SetLogPtrs()
{
	switch (loglevel)
	{	// Set the pointer for the specified level and all higher priorities.
	case LOG_LEVEL_DEBUG:
		debugLog = sharedLog ? sharedLog : stdout;
	case LOG_LEVEL_VERBOSE:
		verboseLog = sharedLog ? sharedLog : stdout;
	case LOG_LEVEL_INFO:
		infoLog = sharedLog ? sharedLog : stdout;
	case LOG_LEVEL_WARNING:
		warnLog = sharedLog ? sharedLog : stderr;
	case LOG_LEVEL_ERROR:
		errorLog = sharedLog ? sharedLog : stderr;
	}
	switch (loglevel)
	{	// Clear the pointer for all priorities lower than that specified.
	case LOG_LEVEL_QUIET:
		errorLog = nullptr;
	case LOG_LEVEL_ERROR:
		warnLog = nullptr;
	case LOG_LEVEL_WARNING:
		infoLog = nullptr;
	case LOG_LEVEL_INFO:
		verboseLog = nullptr;
	case LOG_LEVEL_VERBOSE:
		debugLog = nullptr;
	}
}

WINERR SetLogLevel(LOGGING_LEVEL level)
{
	WINERR err = ::RegSetValueExW(
		regkey,
		LOGLEVELVALUE,
		0x0,
		REG_DWORD,
		reinterpret_cast<PBYTE>(&level),
		sizeof(LOGGING_LEVEL));
	loglevel = level;
	SetLogPtrs();
	LogInfo(L"Logging level set to %d.\n", level);
	return err;
}
WINERR SetLogFile(LPWSTR lpszPath)
{
	WINERR err = ::RegSetValueExW(
		regkey,
		LOGFILEVALUE,
		0x0,
		REG_EXPAND_SZ,
		reinterpret_cast<const PBYTE>(lpszPath),
		((wcslen(lpszPath) + 1) * sizeof(WCHAR)));
	logfile = lpszPath;
	if (sharedLog)
	{	// We're replacing the old log file; close it
		::fclose(sharedLog);
	}
	// Open a Unicode file for logging.
	sharedLog = ::_wfsopen(logfile, L"a+b, ccs=UNICODE", SH_DENYWR);
	if (!sharedLog)
	{
		err = _doserrno;
		// This is awkward, because the user probably can't see our message.
		return err;
	}
	SetLogPtrs();
	LogInfo(L"Log file set to \"%s\".\n", lpszPath);
	return err;
}

// Note serious problems, which always cause the program to exit
void LogError(PCWSTR message, ...)
{
	if (!errorLog)
	{
		return;
	}
	va_list args;
	::fwprintf(errorLog, L"ERROR: ");
	va_start(args, message);
	::vfwprintf(errorLog, message, args);
	va_end(args);
	::fflush(errorLog);
}

// Note unusual or concerning discoveries. By default, execution will continue.
void LogWarning(PCWSTR message, ...)
{
	if (!warnLog)
	{
		return;
	}
	va_list args;
	::fwprintf(warnLog, L"WARNING: ");
	va_start(args, message);
	::vfwprintf(warnLog, message, args);
	va_end(args);
	::fflush(warnLog);
}

// Note significant but expected events in server execution.
void LogInfo(PCWSTR message, ...)
{
	if (!infoLog)
	{
		return;
	}
	va_list args;
	::fwprintf(infoLog, L"INFO: ");
	va_start(args, message);
	::vfwprintf(infoLog, message, args);
	va_end(args);
	::fflush(infoLog);
}

// Note minor information, including every RPC call.
void LogVerbose(PCWSTR message, ...)
{
	if (!verboseLog)
	{
		return;
	}
	va_list args;
	::fwprintf(verboseLog, L"VERBOSE: ");
	va_start(args, message);
	::vfwprintf(verboseLog, message, args);
	va_end(args);
	::fflush(verboseLog);
}

// Note detailed information, useful for debugging.
void LogDebug(PCWSTR message, ...)
{
	if (!debugLog)
	{
		return;
	}
	va_list args;
	::fwprintf(debugLog, L"DEBUG: ");
	va_start(args, message);
	::vfwprintf(debugLog, message, args);
	va_end(args);
	::fflush(debugLog);
}
